package com.icloud.common.web.aspect;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.icloud.common.core.constant.AuthConstant;
import com.icloud.common.core.constant.MobileHeaderConstant;
import com.icloud.common.web.annotation.AppOperationLog;
import com.icloud.common.web.domain.UserDto;
import com.icloud.common.web.enums.ResultEnum;
import com.icloud.common.web.util.ErrorUtil;
import com.icloud.common.web.util.IpUtil;
import com.icloud.common.web.vo.IpAddressVo;
import com.icloud.system.api.dto.SaveAppOperationLogDto;
import com.icloud.system.api.feign.AppOperationLogFeignClient;
import com.nimbusds.jose.JWSObject;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.concurrent.Executor;

@Aspect
@Component
@AllArgsConstructor
@Slf4j
public class AppOperationLogAop {
    private final HttpServletRequest request;
    private final AppOperationLogFeignClient appOperationLogFeignClient;
    private final Executor asyncExecutor;

    /**
     * 定义切入点
     */
    @Pointcut("@annotation(com.icloud.common.web.annotation.AppOperationLog)")
    public void annotationAppOperationLogCut() {

    }

    @Around("annotationAppOperationLogCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = null;
        String failedMessage = null;
        Throwable te = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            te = e;
            log.error("App操作异常：{}", ErrorUtil.getErrorLog(e));
            failedMessage = ErrorUtil.getErrorLog(e);
            if (StringUtils.isNotBlank(failedMessage)) {
                failedMessage = failedMessage.replaceAll("\\s+", " ");
            }
        }
        //保存日志
        this.saveAppOperationLog(joinPoint, result, failedMessage);
        if (te != null) {
            throw te;
        }
        return result;
    }

    /**
     * 执行操作日志入库处理
     */
    private void saveAppOperationLog(ProceedingJoinPoint joinPoint, Object result, String error) {
        //从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获取切入点所在的方法
        Method method = signature.getMethod();
        //获取自定义注解AppOperationLog
        AppOperationLog annotation = method.getAnnotation(AppOperationLog.class);
        //注解值(操作日志描述)
        String appOperationLogVal = annotation.value();
        //请求方式
        String requestMethod = request.getMethod();
        String requestUri = request.getRequestURI();
        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        //请求的方法参数值
        Object[] args = joinPoint.getArgs();
        //请求的方法参数名
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        StringBuilder params = new StringBuilder();
        if (args != null && paramNames != null) {
            for (int i = 0; i < args.length; i++) {
                if (i == 0) {
                    params.append(paramNames[i]).append(": ").append(args[i]);
                } else {
                    params.append("  ").append(paramNames[i]).append(": ").append(args[i]);
                }
            }
        }
        //从token中获取用户信息
        String token = request.getHeader(AuthConstant.JWT_TOKEN_HEADER);
        UserDto userDto = null;
        if (StrUtil.isNotEmpty(token)) {
            try {
                String realToken = token.replace(AuthConstant.JWT_TOKEN_PREFIX, "");
                JWSObject jwsObject = JWSObject.parse(realToken);
                String userStr = jwsObject.getPayload().toString();
                userDto = JSONUtil.toBean(userStr, UserDto.class);
            } catch (ParseException e) {
                log.error("App操作日志解析token异常：{}", e.getMessage());
                return;
            }
        }
        //数据入库
        if (null == userDto) {
            return;
        }
        UserDto finalUserDto = userDto;
        //IP和地址
        String ip;
        String address;
        //获取真实ip和地址
        IpAddressVo ipAddressVo = IpUtil.getIpAndAddress();
        if (null != ipAddressVo) {
            ip = ipAddressVo.getIp();
            address = ipAddressVo.getAddress();
        } else {
            address = "";
            ip = "";
        }
        // 从header中获取客户端信息
        // 设备型号
        String model = request.getHeader(MobileHeaderConstant.MODEL_KEY);
        // 设备品牌
        String brand = request.getHeader(MobileHeaderConstant.BRAND_KEY);
        // 产品名称
        String product = request.getHeader(MobileHeaderConstant.PRODUCT_KEY);
        // 系统版本
        String release = request.getHeader(MobileHeaderConstant.RELEASE_KEY);
        // SDK版本号
        String sdkVersionCode = request.getHeader(MobileHeaderConstant.SDK_VERSION_CODE_KEY);
        // 当前网络运营商名称
        String networkOperator = request.getHeader(MobileHeaderConstant.NETWORK_OPERATOR_KEY);
        // 当前网络类型,3G,4G,5G,wifi
        String networkType = request.getHeader(MobileHeaderConstant.NETWORK_TYPE_KEY);
        // 时区ID
        String timeZoneId = request.getHeader(MobileHeaderConstant.TIME_ZONE_ID_KEY);
        // 用户显示的语言环境的语言名称
        String localeDisplayLanguage = request.getHeader(MobileHeaderConstant.LOCALE_DISPLAY_LANGUAGE_KEY);
        // 手机mac地址(非路由器)
        String mac = request.getHeader(MobileHeaderConstant.MAC_KEY);
        // 手机信号强度
        String dbm = request.getHeader(MobileHeaderConstant.DBM_KEY);
        // 经度
        String longitude = request.getHeader(MobileHeaderConstant.LONGITUDE_KEY);
        // 纬度
        String latitude = request.getHeader(MobileHeaderConstant.LATITUDE_KEY);
        // GPS详细地址信息
        String addressDetails = request.getHeader(MobileHeaderConstant.ADDRESS_DETAILS_KEY);
        // APP版本
        String appVersion = request.getHeader(MobileHeaderConstant.APP_VERSION_KEY);
        // 路由器公网IP,流量访问时无
        String routerIp = request.getHeader(MobileHeaderConstant.ROUTER_IP_KEY);

        asyncExecutor.execute(() -> {
            //总后台的操作日志入库
            SaveAppOperationLogDto appDto = new SaveAppOperationLogDto();
            appDto.setCustomerId(finalUserDto.getId());
            appDto.setUsername(finalUserDto.getUsername());
            //获取真实ip和地址
            appDto.setIp(ip);
            appDto.setAddress(address);
            appDto.setRemark(appOperationLogVal);
            appDto.setRequestMethod(requestMethod);
            appDto.setRequestUri(requestUri);
            appDto.setRequestPath(className + "." + methodName);
            appDto.setRequestParams(params.toString());
            appDto.setModel(model);
            appDto.setBrand(brand);
            appDto.setProduct(product);
            appDto.setRelease(release);
            appDto.setSdkVersionCode(sdkVersionCode);
            appDto.setNetworkOperator(networkOperator);
            appDto.setNetworkType(networkType);
            appDto.setTimezoneId(timeZoneId);
            appDto.setLanguage(localeDisplayLanguage);
            appDto.setMac(mac);
            appDto.setDbm(dbm);
            appDto.setLongitude(longitude);
            appDto.setLatitude(latitude);
            appDto.setAppVersion(appVersion);
            appDto.setRouterIp(routerIp);
            if (StringUtils.isNotBlank(error)) {
                appDto.setRequestResult(ResultEnum.FAILED.getCode());
                appDto.setFailedReason(error);
            } else {
                appDto.setRequestResult(ResultEnum.SUCCESS.getCode());
            }
            appOperationLogFeignClient.saveAppOperationLog(appDto);
        });
    }
}
